﻿using System;
using System.Collections.Generic;
using System.Linq;
using CommandLine;
using CommandLine.Text;

namespace AutoDb {
	/// <summary>
	/// 
	/// </summary>
	class Program {
		private static LogContext m_logContext;

		/// <summary>
		/// 
		/// </summary>
		/// <param name="args"></param>
		[LoaderOptimization(LoaderOptimization.SingleDomain)]
		[STAThread]
		static int Main(string[] args) {
			var options = new Options();

			if (CommandLine.Parser.Default.ParseArguments(args, options)) {
				// Initialize log.
				InitLogContext(options.Verbose, options.LogFile);

				try {
					var startTime = DateTime.Now;
					LogStartTime(startTime);

					IList<Exception> errors = new List<Exception>();
					IList<Exception> warns = new List<Exception>();

					IList<DbModule> modules = ParseSourceFile(options.SourceFile, options.RootPackage, errors, warns);

					CheckModules(modules, errors, warns);

					CreateCodeFile(options.Target, modules, errors, warns);

					LogResult(options.SourceFile, errors, warns);

					var endTime = DateTime.Now;
					LogElapsedTime(startTime, endTime);

					if (errors.Count > 0) {
						return 2;
					} else {
						return 0;
					}
				} catch {
					return 3;
				} finally {
					ReleaseLogContext();
				}
			} else {
				return 1;
			}
		} // end of Main.

		/// <summary>
		/// 
		/// </summary>
		/// <param name="options"></param>
		/// <param name="errors"></param>
		/// <param name="warns"></param>
		/// <returns></returns>
		private static IList<DbModule> ParseSourceFile(string sourceFile, string rootPackage, IList<Exception> errors, IList<Exception> warns) {
			// Initialize source file parser.
			var parser = SourceFileParserFactory.CreateParser(SourceFileType.Xml, m_logContext);

			try {
				// Parse source file.
				m_logContext.Title("\r\n" + Properties.Main.ParsingSourceFile + "\r\n");

				var modules = new List<DbModule>();
				modules.AddRange(parser.Parse(sourceFile, rootPackage));

				var ret = new List<DbModule>();
				foreach (var mg in modules.GroupBy(m => m.Package + "." + m.Name)
					.Select(g => g.ToArray())) {

					if (mg.Length == 0) {
						continue;
					} else if (mg.Length == 1) {
						ret.Add(mg[0]);
					} else {
						ret.Add(DbModule.Merge(mg));
					}
				}

				return ret;
			} catch (Exception ex) {
				errors.Add(ex);

				return new DbModule[0];
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="modules"></param>
		/// <param name="errors"></param>
		/// <param name="warns"></param>
		private static void CheckModules(IList<DbModule> modules, IList<Exception> errors, IList<Exception> warns) {
			foreach (var module in modules) {
				foreach (var stmt in module.Statements) {
					if ((stmt.Name.StartsWith("find") || stmt.Name.StartsWith("load"))
						&& stmt.ResultType == ResultType.UpdatedRows) {
						warns.Add(new InvalidDbObjectException(stmt, Properties.Main.WarnFindOrLoadStatementShouldNotUpdateRows, stmt.Name));
					}
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="target"></param>
		/// <param name="modules"></param>
		/// <param name="errors"></param>
		/// <param name="warns"></param>
		private static void CreateCodeFile(string target, IEnumerable<DbModule> modules, IList<Exception> errors, IList<Exception> warns) {
			// Initialize code file template.
			m_logContext.Title("\r\n" + Properties.Main.InitializingCodeTemplate + "\r\n");
			var codeTemplate = CodeTemplateFactory.CreateTemplate(CodeFileType.CSharp, target, m_logContext);

			try {
				// Create code file(s).
				m_logContext.Title("\r\n" + Properties.Main.CreatingCodeFile + "\r\n");

				foreach (var module in modules) {
					codeTemplate.Render(module);
				}
			} catch (Exception ex) {
				errors.Add(ex);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="startTime"></param>
		private static void LogStartTime(DateTime startTime) {
			m_logContext.Info(GetVersionString());

			m_logContext.Info(Properties.Main.StartTime, startTime);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="sourceFile"></param>
		/// <param name="errors"></param>
		/// <param name="warns"></param>
		private static void LogResult(string sourceFile, IList<Exception> errors, IList<Exception> warns) {
			if (errors.Count == 0) {
				m_logContext.Good("\r\n" + Properties.Main.SucceededBuild + "\r\n", sourceFile);
			} else {
				m_logContext.Error("\r\n" + Properties.Main.FailedBuild + "\r\n", sourceFile);
			}

			m_logContext.Info(Properties.Main.ErrorsCount, errors.Count);
			m_logContext.Info(Properties.Main.WarnsCount + "\r\n", warns.Count);

			for (int i = 0; i < errors.Count; ++i) {
				m_logContext.Error(Properties.Main.Error, i + 1, errors[i]);
			}
			for (int i = 0; i < warns.Count; ++i) {
				m_logContext.Warn(Properties.Main.Warn, i + 1, warns[i]);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="startTime"></param>
		/// <param name="endTime"></param>
		private static void LogElapsedTime(DateTime startTime, DateTime endTime) {
			m_logContext.Info("\r\n" + Properties.Main.ElapsedTime, endTime - startTime);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="logFile"></param>
		/// <param name="verbose"></param>
		/// <returns></returns>
		private static void InitLogContext(bool verbose, string logFile = "") {
			var logContext = new LogContext();
			var clw = new ConsoleLogWriter();
			clw.Level = verbose ? LogLevel.Verbose : LogLevel.Info;
			logContext.AddWriter("console", clw);

			if (!string.IsNullOrEmpty(logFile)) {
				var flw = new FileLogWriter(logFile);
				flw.Level = verbose ? LogLevel.Verbose : LogLevel.Info;
				logContext.AddWriter("file", flw);
			}

			m_logContext = logContext;
		}

		/// <summary>
		/// 
		/// </summary>
		private static void ReleaseLogContext() {
			if (m_logContext != null) {
				m_logContext.Close();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		public static string GetVersionString() {
			var version = typeof(Program).Assembly.GetName().Version;
			return string.Format(Properties.Main.AppVersion, version) + "\r\n";
		} // end of GetVersionString.
	} // end of Program.

	class Options {
		/// <summary>
		/// 
		/// </summary>
		[Option('s', "source", DefaultValue = "statements.xml")]
		public string SourceFile { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Option('p', "package", Required = true)]
		public string RootPackage { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Option("lf", DefaultValue = "")]
		public string LogFile { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Option('t', "target", DefaultValue = ".")]
		public string Target { get; set; }

		/// <summary>
		/// 
		/// </summary>
		[Option('v', "verbose", DefaultValue = false)]
		public bool Verbose { get; set; }

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		[HelpOption]
		public string GetUsage() {
			var sb = new System.Text.StringBuilder();

			sb.AppendLine(Program.GetVersionString());
			sb.AppendLine("autodb -p | --package <root package name> [-s | --source <source file name>] [-t | --target <target directory name>] [--lf <log file name>] [-v | --verbose]");
			sb.AppendLine();
			sb.AppendFormat("  -s | --source <source file name>\t{0}", Properties.Main.CommandLineSourceFile);
			sb.AppendLine();
			sb.AppendFormat("  -p | --package <root package name>\t{0}", Properties.Main.CommandLinePackage);
			sb.AppendLine();
			sb.AppendFormat("  -t | --target <target directory name>\t{0}", Properties.Main.CommandLineTarget);
			sb.AppendLine();
			sb.AppendFormat("  --lf <log file name>\t\t\t{0}", Properties.Main.CommandLineLogFile);
			sb.AppendLine();
			sb.AppendFormat("  -v | --verbose\t\t\t{0}", Properties.Main.CommandLineVerbose);
			sb.AppendLine();

			return sb.ToString();
		}
	} // end of Options.
}
